package com.xcode.unit.xclassloader;

import com.xcode.unit.constant.UnitTestClassConstants;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * @author liuziying
 * @Description 动态类加载器
 * @Date 2021/1/4
 */
public class XDynamicClassLoader extends ClassLoader{

    Logger logger = LoggerFactory.getLogger(this.getClass());

    private static Map<String, byte[]> classMap = new ConcurrentHashMap<>();
    private static final String CLASS_SUFFIX = ".class";
    private static final String JAR_SUFFIX = ".jar";
    public String classPath;
    public String libPath;
 
    public XDynamicClassLoader(String classPath, String libPath) {
        this.classPath = classPath;
        this.libPath = libPath;
        if(!StringUtils.isEmpty(this.classPath)){
            preReadClassFile();
        }
        if(!StringUtils.isEmpty(this.libPath)){
            preReadJarFile();
        }
    }

    public XDynamicClassLoader(ClassLoader parent, String classPath, String libPath){
        super(parent);
        this.classPath = classPath;
        this.libPath = libPath;
        if(!StringUtils.isEmpty(this.classPath)){
            preReadClassFile();
        }
        if(!StringUtils.isEmpty(this.libPath)){
            preReadJarFile();
        }
    }

    public static boolean addClass(String className, byte[] byteCode) {
        if (!classMap.containsKey(className)) {
            classMap.put(className, byteCode);
            return true;
        }
        return false;
    }

    @SuppressWarnings("deprecation")
    @Override
    public Class<?> findClass(String className){
        try {
            byte[] result = getClass(className);
            if (result == null) {
                throw new ClassNotFoundException(className);
            } else {
                return defineClass(className, result, 0, result.length);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        logger.warn("class is not found !");
        return null;
    }

    private byte[] getClass(String className) {
        if (classMap.containsKey(className)) {
            return classMap.get(className);
        } else {
            return null;
        }
    }

    private void preReadClassFile() {
        if(ObjectUtils.isEmpty(classPath)){
            return;
        }
        Collection<File> classFiles =  FileUtils.listFiles(new File(classPath), new String[]{"class"}, true);
        for(File classFile : classFiles){
            scanClassFile(classFile);
        }
        logger.info("加载.class文件：{}", classFiles.size());
    }

    private void scanClassFile(File classFile) {
        try {
            String classAbsolutePath = classFile.getAbsolutePath();
            if(classAbsolutePath != null){
                String qualifiedPath = classAbsolutePath.replace(classPath,"").replace(UnitTestClassConstants.SEPARATOR_SPACE_ONE,"");
                if(qualifiedPath.contains("classes")){
                    qualifiedPath = qualifiedPath.substring(qualifiedPath.indexOf("classes") + 8);
                }
                String qualifiedName = qualifiedPath.replace("\\",".").replace("/",".").replace(CLASS_SUFFIX, "");
                byte[] clazz = Files.readAllBytes(Paths.get(classAbsolutePath));
                addClass(qualifiedName, clazz);
            }
        }catch (Exception e) {
            logger.error("加载clsss文件异常:{}",e.getMessage(),e);
        }
    }

    private void preReadJarFile() {
        if(ObjectUtils.isEmpty(libPath)){
            return;
        }
        try {
            Collection<File> jarFiles =  FileUtils.listFiles(new File(libPath), new String[]{"jar"}, true);
            for(File jarFile : jarFiles){
                readJAR(new JarFile(jarFile));
            }
            logger.info("加载.jar文件：{}", jarFiles.size());
        }catch (IOException e){
            logger.error("加载jar异常：{}", e.getMessage(),e);
        }
    }

    private void readJAR(JarFile jar) throws IOException {
        Enumeration<JarEntry> en = jar.entries();
        while (en.hasMoreElements()) {
            JarEntry je = en.nextElement();
            je.getName();
            String name = je.getName();
            if (name.endsWith(".class")) {
                if(name.contains("classes")){
                    name = name.substring(name.indexOf("classes") + 8);
                }
                String className = name.replace("\\", ".").replace("/", ".").replace(CLASS_SUFFIX, "");
                InputStream input = null;
                ByteArrayOutputStream baos = null;
                try {
                    input = jar.getInputStream(je);
                    baos = new ByteArrayOutputStream();
                    int bufferSize = 1024;
                    byte[] buffer = new byte[bufferSize];
                    int bytesNumRead = 0;
                    while ((bytesNumRead = input.read(buffer)) != -1) {
                        baos.write(buffer, 0, bytesNumRead);
                    }
                    addClass(className, baos.toByteArray());
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    if (baos != null) {
                        baos.close();
                    }
                    if (input != null) {
                        input.close();
                    }
                }
            }
        }
    }

}